//@author A0083782W
#include "SearchEngine.h"

Month MyTime::gotoNextMonth() {
	const CommonDataType::ByteInt currentDay = timeData[(pos_int)TimeType::DAY];
	do {
		MyTime * myself = (MyTime*)this;
		(*myself)++;
	}
	while (currentDay != timeData[(pos_int)TimeType::DAY]);

	return (Month)timeData[(pos_int)TimeType::MONTH];
}

bool MyTime::operator == (const MyTime& rightOperand) {
	if (getTime (TimeType::YEAR) == rightOperand.getTime (TimeType::YEAR) &&
		getTime (TimeType::MONTH) == rightOperand.getTime (TimeType::MONTH) &&
		getTime (TimeType::DAY) == rightOperand.getTime (TimeType::DAY)) {
			return true;
	}
	else {
		return false;
	}
}

MyTime::MyTime (const Time& theOtherTime) {
	reset();
	for (pos_int i = FIRST_MEMBER; i < TIME_MAX; i++ ) {
		timeData[i] = theOtherTime.getTime((TimeType)i);
	}
}

void MyTime::operator = (const Time& rightOperand) {
	for (pos_int i = FIRST_MEMBER; i < TIME_MAX; i++ ) {
		timeData[i] = rightOperand.getTime((TimeType)i);
	}
}

//@author A0101056Y
void SearchEngine::setEverything(const Event &anEvent, const SEARCH_STRUCT &searchInfo, const pos_int &eventIndex){
	_myEvent = anEvent;
	_mySearchInfo = searchInfo; 
	_myEventIndex = eventIndex;
}

vector<SEARCH_RELEVANCE> SearchEngine::getFoundEvents(){
	return _foundEvents;
}

void SearchEngine::searchEvents(){

	bool messageCheck = false;
	bool tagsCheck = false;
	bool timeCheck = false;
	if (_mySearchInfo.messages.size() != 0){
		messageCheck = true;
	}
	if (_mySearchInfo.tags.size() != 0){
		tagsCheck = true;
	}
	if ((_mySearchInfo.beginTime.isEmpty()== false) && (_mySearchInfo.beginTime.isCompletedDate()==true)){
		timeCheck = true;
	}
		
	pos_int match = 0; 
	pos_int distance = 0;

	if (messageCheck && (!tagsCheck) && (!timeCheck)){
		checkDetailsMet(match, distance);
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if ((!messageCheck) && tagsCheck && (!timeCheck)){
		checkTagMet(match, distance);
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if ((!messageCheck) && (!tagsCheck) && timeCheck){
		checkTimeMet(match, distance);
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if (messageCheck && tagsCheck && (!timeCheck)){
		checkDetailsMet(match, distance);
		checkTagMet(match, distance);
		if (match > 0){	
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if (messageCheck && (!tagsCheck) && timeCheck){
		checkDetailsMet(match, distance);
		checkTimeMet(match, distance);
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if ((!messageCheck) && tagsCheck && timeCheck){
		checkTagMet(match, distance);
		checkTimeMet(match, distance);
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}
	if (messageCheck && tagsCheck && timeCheck){
		checkDetailsMet(match, distance);
		checkTagMet(match, distance);
		checkTimeMet(match, distance);		
		if (match > 0){
			setFoundEvents(_foundEvents, _myEventIndex, match, distance);
		}
	}

	for (auto i: _mySearchInfo.filters){
		if (i != SearchFilter::NORMAL){
			if (!(isFiltersMet())){
				if ((messageCheck || tagsCheck || timeCheck) && (_foundEvents.size() != 0)){
					_foundEvents.pop_back();
				}
			}
			else{
				if ((!messageCheck) && (!tagsCheck) && (!timeCheck)){
					match++;
					setFoundEvents(_foundEvents, _myEventIndex, match, distance);
				}
			}
		}
	}
	sort(_foundEvents.begin(), _foundEvents.end(), compareByRelevance);
}


void SearchEngine::checkDetailsMet (pos_int &match, pos_int &distance){

	string targetString = ignoreCase (_myEvent.getDetails());
	string keywordsString = ignoreCase (_mySearchInfo.messages);
	vector<string> targetStringList;
	vector<string> keywordsStringList; 
	vector<pos_int> indicesList;

	splitString (
		keywordsString,
		SEARCH_SEP_CHAR_LIST, 
		SEARCH_SEP_LIST_CHAR_N, 
		keywordsStringList,
		indicesList
	);
	
	splitString (
		targetString, 
		SEARCH_SEP_CHAR_LIST, 
		SEARCH_SEP_LIST_CHAR_N, 
		targetStringList,
		indicesList
	);
	
	foundMatch<string>(keywordsStringList, targetStringList, match, distance);
}


void SearchEngine::checkTagMet (pos_int &match, pos_int &distance){
	vector<string> *ptr = _myEvent.getTagPtr();

	foundMatch<string> (_mySearchInfo.tags, *ptr, match, distance);
}


void SearchEngine::checkTimeMet (pos_int &match, pos_int &distance){

	if (_myEvent.getStartTime().isCompletedDate() == false){
		return;
	}

	vector<MyTime> rangeOfTargetTime;
	vector<MyTime> rangeOfKeywordsTime;

	RepeatFrequency frequency = _myEvent.getRepeatRule().frequency;
	
	MyTime targetTemp = _myEvent.getStartTime();
	MyTime keywordsTemp = _mySearchInfo.beginTime;

	rangeOfKeywordsTime.push_back (_mySearchInfo.beginTime);
 	keywordsTemp++;
	while (keywordsTemp <= _mySearchInfo.endTime) {
		rangeOfKeywordsTime.push_back(keywordsTemp);
		keywordsTemp++;
	}

	switch(frequency) {
	case RepeatFrequency::NO_REPEAT:
		rangeOfTargetTime.push_back(_myEvent.getStartTime());
		if (_myEvent.isDuration() == true) {
			targetTemp++;
			while (targetTemp <= _myEvent.getEndTime()) {
				rangeOfTargetTime.push_back (targetTemp);
				targetTemp++;
			}
		}
		break;
	
	case RepeatFrequency::EVERY_N_DAY:
		if (targetTemp.isCompletedDate() == true) {
			do{
				rangeOfTargetTime.push_back(targetTemp);
				if (_myEvent.isDuration() == true){
					targetTemp++;
					MyTime temp = targetTemp;
					while (temp <= _myEvent.getEndTime()) {
						rangeOfTargetTime.push_back(temp);
						temp++;
					}
				}

				for (pos_int i=0; i<_myEvent.getRepeatRule().period; i++){
					targetTemp ++;
				}

			}while (targetTemp <= _mySearchInfo.endTime);
		}
		break;

	case RepeatFrequency::EVERY_MONTH:
		do {
			rangeOfTargetTime.push_back(targetTemp);

			if (_myEvent.isDuration() == true){
				targetTemp++;
				MyTime temp = targetTemp;
				while (temp <= _myEvent.getEndTime()) {
					rangeOfTargetTime.push_back(temp);
					temp++;
				}
			}
			targetTemp.gotoNextMonth();
		}while (targetTemp <= _mySearchInfo.endTime);
		break;

	default:
		break;
	}

	pos_int tempMatch = 0;
	pos_int temp = distance;
	foundMatch<MyTime> (rangeOfKeywordsTime,rangeOfTargetTime, tempMatch, distance);
	
	distance = temp;
	if (match == 0){
		distance ++;
	}
	match = match + tempMatch;
	
	
}

bool SearchEngine::isFiltersMet() {
	for (auto i: _mySearchInfo.filters){
		switch(i){
		case SearchFilter::DONE:
			if (_myEvent.isDone() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_DONE:
			if (_myEvent.isDone() == false){
				return true;
			}
			break;
	
		case SearchFilter::REMINDED:
			if (_myEvent.isReminded() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_REMINDED:
			if (_myEvent.isReminded() == false){
				return true;
			}
			break;

		case SearchFilter::FLOATING:
			if (_myEvent.getStartTime().isEmpty()){
				return true;
			}
			break;

		case SearchFilter::NOT_FLOATING:
			if (!(_myEvent.getStartTime().isEmpty())){
				return false;
			}
			break;
		
		case SearchFilter::REPEATABLE:
			if (_myEvent.getRepeatRule().frequency != RepeatFrequency::NO_REPEAT){
				return true;
			}
			break;

		case SearchFilter::NOT_REPEATABLE:
			if (_myEvent.getRepeatRule().frequency == RepeatFrequency::NO_REPEAT){
				return true;
			}
			break;

		case SearchFilter::FAVOURITE:
			if (_myEvent.isFavourite() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_FAVOURITE:
			if (_myEvent.isFavourite() == false){
				return true;
			}
			break;

		case SearchFilter::MISSED:
			if (_myEvent.isMissed() == true){
				return true;
			}
			break;

		case SearchFilter::NOT_MISSED:
			if (_myEvent.isMissed() == false){
				return false;
			}
			break;

		default:
			failProgram (ERR_Operations::invalid_search_filter);
			break;
		}
	}
	return false;
}

void SearchEngine::setFoundEvents (
	vector<SEARCH_RELEVANCE> &foundEvents, 
	pos_int index, 
	pos_int &match, 
	pos_int &distance){

	SEARCH_RELEVANCE temp; 
	temp.storageIndex = index;
	temp.relevanceIndex = match - distance;
	foundEvents.push_back(temp);
}